import md5 from 'md5';
import type { AxiosRequestConfig } from 'axios';

/**
 * 声明一个 Map 用于存储每个请求的标识 (key) 和其对应的 AbortController。
 *
 * AbortController 是浏览器和 Node.js 内置的 API，
 * 专门用于中止一个或多个 Web 请求。
 */
const pendingRequests = new Map<string, AbortController>();

/**
 * 根据 axios 请求配置生成一个唯一的 key。
 *
 * 我们使用请求的 URL、方法、查询参数 (params) 和请求体 (data)
 * 来确保对于同一个接口的不同请求能生成不同的 key。
 *
 * @param config - axios 的请求配置对象
 * @returns 返回一个 MD5 加密后的字符串作为唯一 key
 */
export const getRequestKey = (config: AxiosRequestConfig): string => {
  // 如果 config 不存在，则返回一个基于时间戳的 key (作为降级方案)
  if (!config) {
    return md5(Date.now().toString());
  }

  // 将 params 和 data 对象序列化为 JSON 字符串，以确保对象内容的唯一性
  const paramsString = config.params ? JSON.stringify(config.params) : '';
  const dataString = config.data ? JSON.stringify(config.data) : '';

  // 拼接所有关键信息并生成 MD5
  return md5(`${config.url}&${config.method}&${paramsString}&${dataString}`);
};

/**
 * 将一个新的请求添加到 pending 映射中。
 * 在添加之前，它会检查是否已存在相同的请求，如果存在，则先取消旧的请求。
 *
 * @param config - axios 的请求配置对象
 */
export const addPendingRequest = (config: AxiosRequestConfig): void => {
  const requestKey = getRequestKey(config);

  // 如果存在重复的请求，则取消它
  if (pendingRequests.has(requestKey)) {
    const controller = pendingRequests.get(requestKey);
    controller?.abort(); // 调用 abort() 方法取消请求
  }

  // 创建一个新的 AbortController 实例
  const controller = new AbortController();
  // 将 AbortController 的 signal 关联到当前请求的 config 中
  config.signal = controller.signal;
  // 将新的 controller 存入 pending 映射
  pendingRequests.set(requestKey, controller);
};

/**
 * 从 pending 映射中移除一个已完成或已取消的请求。
 *
 * @param config - axios 的请求配置对象
 */
export const removePendingRequest = (config: AxiosRequestConfig): void => {
  const requestKey = getRequestKey(config);
  if (pendingRequests.has(requestKey)) {
    pendingRequests.delete(requestKey);
  }
};

/**
 * 清空所有 pending 中的请求 (例如，在路由跳转时调用)
 */
export const clearPendingRequests = (): void => {
  pendingRequests.forEach((controller) => {
    controller.abort(); // 取消所有正在进行的请求
  });
  pendingRequests.clear(); // 清空 map
};
